﻿/*
 *  Service.cs
 *  This program implements a window service that provides a web service for url shorting.
 *  Cody Bumgardner cody@codybum.com http://codybum.com 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using System.Web;
using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Linq;


using System.ComponentModel;
using System.ServiceModel;
using System.ServiceProcess;
using System.Configuration;
using System.Configuration.Install;

using System.Diagnostics;


namespace URL.urlWService
{
    // Define a service contract.
    [ServiceContract(Namespace = "http://URL.urlWService")]
    public interface Iurl
    {
        
        [OperationContract]
        string getURL(string base36String);
        [OperationContract]
        string addURL(string inputUrl);
        [OperationContract]
        string getFriendlyUrl(string friendlyName);
        [OperationContract]
        string addFriendlyUrl(string friendlyName, string targetUrl);
    }

    // Implement the ICalculator service contract in a service class.
    public class urlService : Iurl
    {
        private static Mutex mut = new Mutex();
        public static bool fileLock = false;
        public static string appfileName = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\afile.bin";
        public static string cachefileName = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\cfile.bin";

        public static string[] urlList;
        public static int urlCount;
        
        public static bool isInit = false;
        public urlService()
        {
            if (!isInit)
            {
                urlList = new string[1679616];
                urlCount = 0;
                readURLCache();
                isInit = true;
            }
        }
        public static bool readURLCache()
        {
            try
            {
            // Create the new, empty data file.
            int count;
            FileStream dfs = null; //data file stream
            if (!File.Exists(appfileName))
            {
                EventLog.WriteEntry("urlWS", appfileName + " does not exist", EventLogEntryType.Warning);
                
                count = 0;
            }
            else
            {
                dfs = new FileStream(appfileName, FileMode.Open, FileAccess.Read);
                BinaryReader dr = new BinaryReader(dfs);
                // Read data from Test.data.
                count = dr.ReadInt32();
                EventLog.WriteEntry("urlWS", "There are " + count + " sites found in cache");
                
                dr.Close();
                dfs.Close();
            }


            if (File.Exists(cachefileName))
            {

                FileStream fs = new FileStream(cachefileName, FileMode.Open, FileAccess.Read);

                BinaryReader r = new BinaryReader(fs);
                StringBuilder sb = new StringBuilder();
                long filelength = r.BaseStream.Length;
                while (r.BaseStream.Position < filelength)
                {
                    char charvar = r.ReadChar();

                    if (charvar.Equals((char)'\0'))
                    {
                        if (urlCount > count)
                        {
                            EventLog.WriteEntry("urlWS", "Read Count larger than cache count", EventLogEntryType.Error);
                        }
                        urlList[urlCount] = sb.ToString();
                        urlCount++;
                        sb.Length = 0;
                    }
                    else
                    {
                        sb.Append(charvar);
                    }

                }

                r.Close();
                fs.Close();
                if (urlCount == count)
                {
                    EventLog.WriteEntry("urlWS", "On cache load FileCount and ReadCount Match.");
                }
            }
            else
            {
                EventLog.WriteEntry("urlWS", cachefileName + " does not exists!", EventLogEntryType.Warning);
            }

            return true;
            }
            catch (Exception ex)
            {
            EventLog.WriteEntry("urlWS","readURLCache: " + ex.Message, EventLogEntryType.Error);
            return false;
            }
        }
        public static int existURL(string inputUrl)
        {
            try
            {
                
                for (int i = 0; i < urlCount; ++i)
                {
                    if (urlList[i].Equals(inputUrl))
                    {
                        return i;
                    }

                }
                return -1;
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("urlWS", "existURL: " + ex.Message, EventLogEntryType.Error);
                return -1;
            }

        }
        public string getURL(string base36String)
        {
            try
            {
                if (base36String.Length < 5)
                {
                    UInt64 indexOfUrl = Base36ToUInt64(base36String);
                    if (indexOfUrl < (UInt64)urlCount)
                    {
                        return urlList[indexOfUrl];
                    }
                    else
                    {
                        EventLog.WriteEntry("urlWS", "getURL: Calculated Index > urlCount.", EventLogEntryType.Warning);
                        return "Calculated Index > urlCount.";
                        
                    }
                }
                else if (base36String.Length >= 5)
                {
                    string targetUrl = null;
                    targetUrl = getFriendlyUrl(base36String);
                    if (targetUrl != null)
                    {
                        return targetUrl;
                    }
                    else
                    {
                        //return "Invalid 4 value base36 string.";
                        return ConfigurationSettings.AppSettings["baseUrl"]; 

                        
                    }
                }
                else
                {
                    EventLog.WriteEntry("urlWS", "getURL: Invalid 4 value base36 string.", EventLogEntryType.Warning);
                    return "Invalid 4 value base36 string.";
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("urlWS", "getURL: " + ex.Message, EventLogEntryType.Error);
                //return "Url Error";
                return ConfigurationSettings.AppSettings["baseUrl"] + "getURL.aspx";
            }

        }

        public string getFriendlyUrl(string friendlyName)
        {
            try
            {
                XDocument fUrlDB = null;
                fUrlDB = XDocument.Load(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\fUrlDB.xml");

                var q = from c in fUrlDB.Descendants("fUrl")

                        where (string)c.Element("friendlyName") == friendlyName

                        select (string)c.Element("targetUrl");

                string targetUrl = null;
                foreach (string name in q)
                {
                    targetUrl = name;
                }
                return targetUrl;
                //Console.WriteLine("Customer name = {0}", name);
            }
            catch
            {
                string targetUrl = null;
                return targetUrl;
            }
        }
        public string addFriendlyUrl(string friendlyName, string targetUrl)
        {
            
            //	PURPOSE:	If the XML file exists append data to it.
            //			If the XML file does not exist, create a new one.
            //
            //	NOTE:		The below method can and should be fitted 
            //                  	into an object Oriented model.
            //                   
            //	USE:		Use the following any way you see fit, 
            //                  	I do not need credit.

            string FullPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\fUrlDB.xml";
            string endElement = "</urls>";
            System.Xml.XmlTextWriter tw;
            long id = System.DateTime.Now.Ticks;
            bool found = false;
            try
            {
                byte[] endtag = System.Text.Encoding.UTF8.GetBytes("");

                if (System.IO.File.Exists(FullPath))
                {

                    found = true;
                    System.IO.FileStream fs = System.IO.File.OpenWrite(FullPath);
                    fs.Seek(-endtag.Length - 7, System.IO.SeekOrigin.End);
                    tw = new System.Xml.XmlTextWriter(fs, System.Text.Encoding.UTF8);

                }
                else
                {

                    string xmlInnerText = "Friendly URL DB";
                    tw = new System.Xml.XmlTextWriter(FullPath, System.Text.Encoding.UTF8);
                    tw.WriteStartDocument(true);
                    tw.WriteComment(xmlInnerText);
                    tw.WriteStartElement("urls");//START PARENT
                }

                tw.Indentation = 4;
                tw.IndentChar = System.Convert.ToChar(" ");
                tw.Formatting = System.Xml.Formatting.Indented;

                tw.WriteStartElement("fUrl");// START CHILD 
                //tw.WriteAttributeString("Date", System.DateTime.Now.ToLongDateString() +
                //                " " + System.DateTime.Now.ToShortTimeString());
                //tw.WriteAttributeString("SeqID", id.ToString());
                tw.WriteElementString("friendlyName", friendlyName);
                tw.WriteElementString("targetUrl", targetUrl);
                //tw.WriteElementString("Msg", msg);
                //tw.WriteEndElement();//END CHILD
                tw.WriteEndElement();//END CHILD

                if (!found)
                {


                    tw.WriteEndElement();//END PARENT
                    tw.WriteEndDocument();

                }
                else
                {

                    //tw.WriteEndElement();//END PARENT
                    //XmlConvert.EncodeName("</Parent>");
                    //tw.WriteString(XmlConvert.EncodeName("</Parent>"));
                    tw.WriteRaw(endElement);
                    tw.Flush();
                    tw.BaseStream.Write(endtag, 0, endtag.Length);

                }
                /*
                while (tw != null)
                {
                    Console.WriteLine("tw not null");
                
                    tw.Flush();

                    Console.WriteLine("end tw not null");
                
                }
                */
                while (tw.WriteState != System.Xml.WriteState.Closed)
                {

                    tw.Close();
                }
                return "1";
            }
            catch
            {
                return "-1";
                //Console.WriteLine(ex.Message);
                //throw new System.IO.FileNotFoundException("File " + FullPath + " not found");
            }

        }
        public string addURL(string inputUrl)
        {
            try
            {
                int indexOfUrl = existURL(inputUrl);
                if (indexOfUrl > -1)
                {

                    string outputUrl = ConfigurationSettings.AppSettings["baseUrl"] + UInt64ToBase36((UInt64)(indexOfUrl));
                    return outputUrl;
                }
                else
                {
                    // Wait until it is safe to enter.
                    mut.WaitOne();
                    FileStream fs = new FileStream(cachefileName, FileMode.Append, FileAccess.Write, FileShare.None);
                    // Create the writer for data.
                    BinaryWriter w = new BinaryWriter(fs);
                    urlList[urlCount] = inputUrl;
                    urlCount++; //local count

                    inputUrl = inputUrl + '\0';
                    w.Write(inputUrl.ToCharArray());
                    //w.Write((char)'\0');
                    w.Close();
                    fs.Close();

                    FileStream dfs = null; //data file stream
                    dfs = new FileStream(appfileName, FileMode.OpenOrCreate, FileAccess.Write);
                    BinaryWriter dw = new BinaryWriter(dfs);
                    dw.Write((int)urlCount);
                    dw.Close();
                    dfs.Close();
                    mut.ReleaseMutex();

                    string outputUrl = ConfigurationSettings.AppSettings["baseUrl"] + UInt64ToBase36((UInt64)(urlCount - 1));
                    EventLog.WriteEntry("urlWS", "outputurl=" + outputUrl);
               
                    return outputUrl;

                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("urlWS", "addURL: " + ex.ToString(), EventLogEntryType.Error);

                return ex.Message.ToString();
            }
        }
        public static UInt64 Base36ToUInt64(String base36String)
        {
            try
            {
                String s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

                base36String = base36String.ToUpper();
                Int32 i = 0, pos = 0;
                UInt64 rv = 0;
                do
                {
                    pos = s.IndexOf(base36String[i]);
                    rv = (rv * 36) + (UInt32)pos;
                    i++;
                }
                while (i < base36String.Length);
                return rv;
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("urlWS", "Base36ToUInt64: " + ex.Message, EventLogEntryType.Error);
                return 0;
            }
        }
        public static String UInt64ToBase36(UInt64 uInt64)
        {
            try
            {
                String s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

                UInt32 i = 12;
                UInt64 r = 0;
                Char[] c = new char[i + 1];
                do
                {
                    r = (uInt64 % 36);
                    c[i] = s[(Int32)r];
                    uInt64 = (uInt64 - r) / 36;
                    i--;
                }
                while (uInt64 > 0);
                Char[] trimChars = { '\0' };
                return new String(c).Trim(trimChars);
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("urlWS", "UInt64ToBase36: " + ex.Message, EventLogEntryType.Error);
                return ex.Message;
            }
        }
        
    
    }

    public class urlWindowsService : ServiceBase
    {
        
        public ServiceHost serviceHost = null;
        public urlWindowsService()
        {
            // Name the Windows Service
            ServiceName = "urlWS";
        }

        public static void Main()
        {
            ServiceBase.Run(new urlWindowsService());
        }

        // Start the Windows service.
        protected override void OnStart(string[] args)
        {
            if (serviceHost != null)
            {
                serviceHost.Close();
            }

            // Create a ServiceHost for the CalculatorService type and 
            // provide the base address.
            serviceHost = new ServiceHost(typeof(urlService));
            
            // Open the ServiceHostBase to create listeners and start 
            // listening for messages.
            serviceHost.Open();
        }

        protected override void OnStop()
        {
            if (serviceHost != null)
            {
                serviceHost.Close();
                serviceHost = null;
            }
        }
    }

    // Provide the ProjectInstaller class which allows 
    // the service to be installed by the Installutil.exe tool
    [RunInstaller(true)]
    public class ProjectInstaller : Installer
    {
        private ServiceProcessInstaller process;
        private ServiceInstaller service;

        public ProjectInstaller()
        {
            process = new ServiceProcessInstaller();
            process.Account = ServiceAccount.LocalSystem;
            service = new ServiceInstaller();
            service.ServiceName = "urlWS";
            Installers.Add(process);
            Installers.Add(service);
        }
    }
}
